今のはスイッチロールではない…AssumeRole からのフェデレーションサインインだ…をやってみた
コンバンハ、千葉(幸)です。
…今のはスイッチロールでは無い… AssumeRole からのフェデレーションサインインだ…
そんなセリフを言いたくなったことはありませんか?私はありません。「AssumeRole からのフェデレーションサインイン」なんて言い回しはきっとこの地球上に存在しないので、ありません。
ここでのスイッチロールとは「① IAM ユーザーのユーザー名とパスワードを利用してマネジメントコンソールにサインインする」「② IAM ロールに切り替える」の流れを指しています。
IAM ロールの認証情報を利用してマネジメントコンソールで操作したい場合には、このスイッチロールが圧倒的にポピュラーな手法だと思います。しかしこれが唯一の手法ではありません。
IAM ロールを引き受けたセッションをフェデレーテッドユーザーとしてサインインすることもできます。ユーザー名やパスワードを入力することなくマネジメントコンソール上で操作ができてしまうのです。
そんな手法を最近知ったので、実際に試してみます。
フェデレーテッドユーザーによるコンソールアクセス
今回やりたいことの概要は以下です。
- AssumeRole による一時的な認証情報(アクセスキー/シークレットアクセスキー/セッショントークン)を取得する
- 認証情報を含んだリクエストを AWS フェデレーションエンドポイントに実行し、サインイントークンを取得する
- サインイントークンを含む URL を生成し、コンソールアクセスを行う
細部はかなり端折っていますが、以下ページにある内容が該当します。
そして上記ページの内容を実践しているのが以下エントリです。ここでは最初の認証情報の取得を AssumeRole ではなく GetFederationToken によって行っています。
上記のエントリを丸パ……存分に参考にしながら、AssumeRole を用いるパターンでやってみるのが今回の内容です。
フェデレーテッドユーザー用 URL を生成する シェルスクリプト
早速ですが AssumeRole 向けにカスタマイズしたスクリプトが以下です。ハイライト部がオリジナルから改修した部分です。
#!/bin/bash set -e # urlencode用のfunction定義 urlencode() { local length="${#1}" for (( i = 0; i < length; i++ )); do local c="${1:i:1}" case $c in [a-zA-Z0-9.~_-]) printf "$c" ;; *) printf '%%%02X' "'$c" esac done } # 引き受けるIAMロールとセッション名と有効期限を指定 ROLE_ARN=arn:aws:iam::000000000000:role/test ROLE_SESSION_NAME=test-user DURATION_SECONDS=3600 # FederationToken取得(有効期間を調節したい場合には、--durationオプションを変更) FEDERATION_TOKEN=$(aws sts assume-role --role-arn ${ROLE_ARN} --role-session-name ${ROLE_SESSION_NAME} --duration-seconds ${DURATION_SECONDS}) # セッション文字列生成 SESSION_ID=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.AccessKeyId" | tr -d "\"") SESSION_KEY=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.SecretAccessKey" | tr -d "\"") SESSION_TOKEN=$(echo ${FEDERATION_TOKEN} | jp.py "Credentials.SessionToken" | tr -d "\"") # SigninToken取得 JSON_FORMED_SESSION=$(echo "{\"sessionId\":\"${SESSION_ID}\",\"sessionKey\":\"${SESSION_KEY}\",\"sessionToken\":\"${SESSION_TOKEN}\"}") SIGNIN_URL="https://signin.aws.amazon.com/federation" GET_SIGNIN_TOKEN_URL="${SIGNIN_URL}?Action=getSigninToken&SessionType=json&Session=$(urlencode ${JSON_FORMED_SESSION})" SIGNIN_TOKEN=$(curl -s "${GET_SIGNIN_TOKEN_URL}" | jp.py "SigninToken" | tr -d "\"") # LOGIN URLの出力 CONSOLE_URL="https://console.aws.amazon.com/" LOGIN_URL="${SIGNIN_URL}?Action=login&Destination=$(urlencode ${CONSOLE_URL})&SigninToken=$(urlencode ${SIGNIN_TOKEN})" && echo ${LOGIN_URL}
また、オリジナルでは存在していたISSUER_URL
(最終的な URL のパラメータの一部)を動作に必須でないため削っています。
このスクリプトを実行すると、以下形式の URL が出力されます。
https://signin.aws.amazon.com/federation?Action=login&Destination=https%3A%2F%2Fconsole.aws.amazon.com%2F&SigninToken=aQG2h7cOALssYnyq...
末尾のサインイントークンは実際にはもっと長く、1500 字程度の長さがあります。
フェデレーテッドユーザーとしてのサインイン
上記で生成された URL にブラウザからアクセスしてみます。今回はシークレットウインドウでアクセスします。
以下のようにフェデレーテッドユーザーとしてサインインできました。今回は AssumeRole するロールとしてtest
、セッション名としてtest-user
を指定しましたが、それがユーザー名に反映されているのが読み取れます。
この状態から、AssumeRole した IAM ロールが持つ権限の範囲内でコンソール上での操作ができます。このあたりはスイッチロールと変わりありません。AssumeRole 時にセッションポリシーを指定できるため、フェデレーテッドユーザー方式の方がより細やかに制御できる、とも言えます。
このフェデレーテッドユーザーが実行したアクションは CloudTrail イベントでは以下のように記録されていました。
{ "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "AROAQ3BIIH73U2LGAXTZQ:test-user", "arn": "arn:aws:sts::000000000000:assumed-role/test/test-user", "accountId": "000000000000", "accessKeyId": "ASIAQ3BIIH736XGFSDD2", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROAQ3BIIH73U2LGAXTZQ", "arn": "arn:aws:iam::000000000000:role/test", "accountId": "000000000000", "userName": "test" }, "webIdFederationData": {}, "attributes": { "creationDate": "2021-12-28T11:17:07Z", "mfaAuthenticated": "false" } } }, "eventTime": "2021-12-28T11:17:53Z", "eventSource": "ec2.amazonaws.com", "eventName": "DescribeInstanceStatus", "awsRegion": "ap-northeast-1", "sourceIPAddress": "219.xx.xx.xx", "userAgent": "EC2ConsoleFrontend, aws-internal/3 aws-sdk-java/1.12.100 Linux/5.4.156-94.273.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.312-b07 java/1.8.0_312 vendor/Oracle_Corporation cfg/retry-mode/standard", "requestParameters": { "instancesSet": { "items": [ { "instanceId": "i-047875da17caa9cc2" }, { "instanceId": "i-07856803079a58378" }, { "instanceId": "i-0986014d98308ac67" } ] }, "filterSet": {}, "includeAllInstances": false }, "responseElements": null, "requestID": "bd38f7f6-d4ff-4b29-b59e-6018fe5f41dd", "eventID": "eb4f51db-95ac-4eeb-a688-c63f30a424af", "readOnly": true, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "000000000000", "eventCategory": "Management" }
Trail イベント上でのユーザー名はtest-user
です。ロールを引き受ける際に指定したセッション名がここに現れるようです。スイッチロールパターンでもuserIdentity
は同じように表示されるため、イベントだけを見てどちらのパターンでコンソールアクセスをしたかの判別は難しそうです。
おまけ:Duration 指定のエラー
AssumeRole の存続期間は最大で 12 時間のため、--duration-seconds
の値として43200
を指定したところ以下のエラーが出ました。
An error occurred (ValidationError) when calling the AssumeRole operation: The requested DurationSeconds exceeds the MaxSessionDuration set for this role.
IAM ロールのパラメータ「最大セッション時間」を超える値はここでは指定できないため、まずはそちらを延ばしてあげる必要があります。
終わりに
AssumeRole で取得した一時的な認証情報を使用してフェデレーテッドユーザーとしてコンソールにアクセスしてみました。
この手法であれば IAM ユーザーとしてのコンソールアクセスは不要で、パスワードを管理する必要がありません。AssumeRole ができる権限だけ持たせてアクセスキー/シークレットアクセスキーを払い出しておけば事足ります。
基本的にはアプリケーションでの使用が想定されているため今回のように手動で URL を作成するのは手間が大きいかと思いますが、選択肢のひとつとしてこういった手法もあると覚えておくと捗るかもしれません。外部の人に一時的にコンソールを覗いてもらう、といった場面でも使えそうですね。
後から気づきましたが以下のエントリでもほとんど同じようなことを試していますので、合わせてご参考ください。
以上、 チバユキ (@batchicchi) がお送りしました。